package com.guli.sms.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.guli.common.constant.SecKillConstant;
import com.guli.common.pojo.vo.StatusCodeEnum;
import com.guli.common.to.SpuAndSkuListTo;
import com.guli.common.utils.R;
import com.guli.mall.serviceBase.globalException.MallException;
import com.guli.sms.entity.SeckillPromotionEntity;
import com.guli.common.pojo.SeckillSessionEntity;
import com.guli.sms.feign.ProductFeignClient;
import com.guli.sms.feign.SecKillFeignClient;
import com.guli.sms.service.SeckillPromotionService;
import com.guli.sms.service.SeckillSessionService;
import com.guli.sms.utils.DateUtil;
import com.guli.sms.vo.SeckillSkuRelationWithNamesVo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.guli.sms.mapper.SeckillSkuRelationMapper;
import com.guli.common.pojo.SeckillSkuRelationEntity;
import com.guli.sms.service.SeckillSkuRelationService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

//@Aspect
@Service("seckillSkuRelationService")
@Slf4j
public class SeckillSkuRelationServiceImpl extends ServiceImpl<SeckillSkuRelationMapper, SeckillSkuRelationEntity> implements SeckillSkuRelationService {

    @Resource
    SeckillPromotionService seckillPromotionService;

    @Resource
    SeckillSessionService seckillSessionService;

    @Resource
    ProductFeignClient productFeignClient;

    @Resource
    SecKillFeignClient secKillFeignClient;

    @Override
    public Page<SeckillSkuRelationWithNamesVo> queryPage(Integer currentPage, Integer size, Long sessionId) throws ExecutionException, InterruptedException {

        Page<SeckillSkuRelationEntity> page = new Page<>(currentPage, size);

        QueryWrapper<SeckillSkuRelationEntity> wrapper = null;

        if (!ObjectUtils.isEmpty(sessionId)) {
            wrapper = new QueryWrapper<>();
            wrapper.eq("promotion_session_id", sessionId);
        }

        Page<SeckillSkuRelationEntity> page1 = this.page(page, wrapper);


        Map<Long, SpuAndSkuListTo> spuAndSkuListToMap = setSpuInfo(page1.getRecords());


        List<SeckillSkuRelationWithNamesVo> collect = page1.getRecords().parallelStream().map(relation -> {

            // 查询秒杀活动
            SeckillPromotionEntity promotionEntity = seckillPromotionService.getById(relation.getPromotionId());

            SeckillSkuRelationWithNamesVo seckillSkuRelationWithNamesVo = new SeckillSkuRelationWithNamesVo();

            BeanUtils.copyProperties(relation, seckillSkuRelationWithNamesVo);

            seckillSkuRelationWithNamesVo.setSeckillPromotionEntity(promotionEntity);

            // 查询活动场次
            SeckillSessionEntity sessionEntity = seckillSessionService.getById(relation.getPromotionSessionId());

            seckillSkuRelationWithNamesVo.setSeckillSessionEntity(sessionEntity);

            //TODO 查询商品信息
            SpuAndSkuListTo spuAndSkuListTo = spuAndSkuListToMap.get(relation.getSpuId());

            seckillSkuRelationWithNamesVo.setSpuAndSkuListTo(spuAndSkuListTo);

            return seckillSkuRelationWithNamesVo;

        }).collect(Collectors.toList());

        Page<SeckillSkuRelationWithNamesVo> seckillSkuRelationWithNamesVoPage = new Page<>();

        BeanUtils.copyProperties(page1, seckillSkuRelationWithNamesVoPage);

        seckillSkuRelationWithNamesVoPage.setRecords(collect);


        return seckillSkuRelationWithNamesVoPage;
    }

    private Map<Long, SpuAndSkuListTo> setSpuInfo(List<SeckillSkuRelationEntity> relationList) {
        List<Long> skuIds = relationList.stream().map(relation -> {
            return relation .getSpuId();
        }).collect(Collectors.toList());

        if (CollectionUtils.isEmpty(skuIds)) {
            return new HashMap<Long, SpuAndSkuListTo>();
        }

        return productFeignClient.getSpuAndSkuBySpuId(skuIds);

    }

    @Override
    public Page<SeckillSkuRelationEntity> getSeckillRelationsBySeckillSessionId(long seckillSessionId, Integer currentPage, long size) {

        QueryWrapper<SeckillSkuRelationEntity> wrapper = new QueryWrapper<>();

        wrapper.eq("promotion_session_id", seckillSessionId);

        Page<SeckillSkuRelationEntity> seckillSkuRelationEntityPage = new Page<>(currentPage, size);

        return this.page(seckillSkuRelationEntityPage, wrapper);
    }

    @Override
    public List<SeckillSkuRelationEntity> getSeckillRelationsBySeckillSessionId(Long sessionId) {

        QueryWrapper<SeckillSkuRelationEntity> wrapper = new QueryWrapper<>();

        wrapper.eq("promotion_session_id", sessionId);

        return this.list(wrapper);
    }

    /**
     * 不能添加事务，否则在事务中删除relation没有提交，而这边在等待缓存刷新
     * 但是缓存刷新的时候，要获取relation列表，由于这边还没有提交，所以查询到的是老数据，（脏读）
     * 一、不能添加事务
     * 二、数据库删除操作要在刷新缓存操作之前
     * 三、删除操作也不能放在最前，不然后面的根据 relationIds 查询到的 relation 就是 null，再根据这个null的 sessionId 去查询场次信息，就会有 sql异常
     * 四、使用 aop 就能对数据库使用 @Transactional 啦
     *
     * @param relationIds
     */
//    @Transactional
    @Override
    public void removeByIdsAndInvalidCache(List<Long> relationIds) {

        if (CollectionUtils.isEmpty(relationIds)) {
            return;
        }


        List<SeckillSkuRelationEntity> relationEntities = this.getBaseMapper().selectBatchIds(relationIds);

        List<Long> sessionIds = relationEntities.stream().map(relation -> {
            return relation.getPromotionSessionId();
        }).collect(Collectors.toList());

        List<SeckillSessionEntity> seckillSessionEntities = this.seckillSessionService.getBaseMapper().selectBatchIds(sessionIds);

//        long nowTime = new Date().getTime();

        // redis 最新缓存的日期
        Date redisLatestDate = DateUtil.timeAfterDays(new Date(), SecKillConstant.PRE_UPLOAD_DAYS);


        // 要删除的秒杀商品，目前正在秒杀中
        List<SeckillSessionEntity> sessionsInSeckillNow = seckillSessionEntities.stream().filter(session -> {

            return session.getStartTime().getTime() < redisLatestDate.getTime();
//            return nowTime > session.getStartTime().getTime() && nowTime < session.getEndTime().getTime();

        }).collect(Collectors.toList());

        if (!CollectionUtils.isEmpty(sessionsInSeckillNow)) {
//            log.warn("要删除的秒杀商品，目前正在秒杀中，现在执行刷新商品缓存，该商品（relation关联），所在的活动信息是：{}",sessionsInSeckillNow);


            ((SeckillSkuRelationService) AopContext.currentProxy()).deleteBatchIdsWithTransactional(relationIds);
//            this.getBaseMapper().deleteBatchIds(relationIds);

            log.warn("要删除的秒杀商品，已经被写入redis，现在执行刷新商品缓存，该商品（relation关联），所在的活动信息是：{}", sessionsInSeckillNow);

            R r = secKillFeignClient.refreshCache();

            if (!r.getCode().equals(StatusCodeEnum.SUCCESS.getCode())) {
                throw new MallException(StatusCodeEnum.REFRESH_SEC_KILL_SPU_FAILED);
            }
        } else {
            log.info("要删除的秒杀商品，当前没有在秒杀中，可以直接删除");
        }

    }

    /**
     * 1、不能加  @Transactional  注解，不然更新缓存的时候，查询不到这个刚插入的数据（因为还在事务中）
     *
     * @param seckillSkuRelationList
     */

//    @Transactional
    @Override
    public void saveBatchAndCheckIfRedisNeedesToRefresh(List<SeckillSkuRelationEntity> seckillSkuRelationList) {

        SeckillSkuRelationService o = (SeckillSkuRelationService) AopContext.currentProxy();

        o.saveBatchWithLocalTransactional(seckillSkuRelationList);

        checkStartDateIfNeedToRefreshRedis(seckillSkuRelationList);

    }

    /**
     * 根据秒杀活动的开始日期来判断是否已经存入了redis，如果已经存入了redis，则需要刷新缓存
     * @param seckillSkuRelationList
     */
    private void checkStartDateIfNeedToRefreshRedis(List<SeckillSkuRelationEntity> seckillSkuRelationList) {
        List<Long> relationIds = seckillSkuRelationList.stream().map(relation -> {
            return relation.getPromotionSessionId();
        }).collect(Collectors.toList());

        List<SeckillSessionEntity> seckillSessionEntities = seckillSessionService.getBaseMapper().selectBatchIds(relationIds);

        // redis 最新的 场次日期
        Date secKillSkuLatestDate = DateUtil.timeAfterDays(new Date(), SecKillConstant.PRE_UPLOAD_DAYS);

        // 需要刷新的场次信息
        List<SeckillSessionEntity> needToBeRefreshedSessions = seckillSessionEntities.stream().filter(session -> {
            // 过滤，得到已经上架到redis的场次信息
            return session.getStartTime().getTime() < secKillSkuLatestDate.getTime();
        }).collect(Collectors.toList());


        if (!CollectionUtils.isEmpty(needToBeRefreshedSessions)) {
            R r = this.secKillFeignClient.refreshCache();

            if (r.getCode().equals(StatusCodeEnum.SUCCESS.getCode())) {
                return;
            }
            throw new MallException(StatusCodeEnum.REFRESH_SEC_KILL_SPU_FAILED);
        }
    }

    @Transactional
    @Override
    public void saveBatchWithLocalTransactional(List<SeckillSkuRelationEntity> seckillSkuRelationList) {
        this.saveBatch(seckillSkuRelationList);
    }

    @Transactional
    @Override
    public void deleteBatchIdsWithTransactional(List<Long> relationIds) {
        this.getBaseMapper().deleteBatchIds(relationIds);
    }

    @Override
    public void updateByIdAndCheckIfRedisNeedesToRefresh(SeckillSkuRelationEntity seckillSkuRelation) {

        this.updateById(seckillSkuRelation);

        this.checkStartDateIfNeedToRefreshRedis(Arrays.asList(seckillSkuRelation));

    }

}